import { LunarBase } from "../base";
const { lunarYearArr, earthly, heavenly, lunarDayMap, lunarMonthMap } =
  LunarBase;

/** 公历转农历函数
 *
 * @param currentDate 当前公历日期
 * @returns 农历
 */
export function solarToLunar(currentDate: Date) {
  // 计算与公历基准的相差天数
  let daySpan =
    Math.ceil(
      (currentDate.getTime() - Date.UTC(1949, 0, 29)) / (24 * 60 * 60 * 1000)
    ) + 1;

  let lunarYear, lunarMonth, lunarDate;
  // 确定输出的农历年份
  for (let j = 0; j < lunarYearArr.length; j++) {
    daySpan -= lunarYearDays(lunarYearArr[j]);
    if (daySpan <= 0) {
      lunarYear = 1949 + j;
      // 获取农历年份确定后的剩余天数
      daySpan += lunarYearDays(lunarYearArr[j]);
      break;
    }
  }

  // 确定输出的农历月份
  for (
    let k = 0;
    k < lunarYearMonths(lunarYearArr[lunarYear! - 1949]).length;
    k++
  ) {
    daySpan -= lunarYearMonths(lunarYearArr[lunarYear! - 1949])[k];
    if (daySpan <= 0) {
      // 有闰月时，月份的数组长度会变成13，因此，当闰月月份小于等于k时，lm不需要加1
      if (
        hasLeapMonth(lunarYearArr[lunarYear! - 1949]) &&
        (hasLeapMonth(lunarYearArr[lunarYear! - 1949]) as number) <= k
      ) {
        if ((hasLeapMonth(lunarYearArr[lunarYear! - 1949]) as number) < k) {
          lunarMonth = k;
        } else if (hasLeapMonth(lunarYearArr[lunarYear! - 1949]) === k) {
          lunarMonth = "闰" + k;
        } else {
          lunarMonth = k + 1;
        }
      } else {
        lunarMonth = k + 1;
      }
      // 获取农历月份确定后的剩余天数
      daySpan += lunarYearMonths(lunarYearArr[lunarYear! - 1949])[k];
      break;
    }
  }

  // 确定输出农历哪一天
  lunarDate = daySpan;

  // 将计算出来的农历月份转换成汉字月份，闰月需要在前面加上闰字
  if (
    hasLeapMonth(lunarYearArr[lunarYear! - 1949]) &&
    typeof lunarMonth === "string" &&
    lunarMonth.indexOf("闰") > -1
  ) {
    const leapMonth = Number(/\d/.exec(lunarMonth)) - 1;
    lunarMonth = `闰${lunarMonthMap[leapMonth]}`;
  } else {
    lunarMonth = lunarMonthMap[(lunarMonth as number) - 1];
  }

  // 将计算出来的农历年份转换为天干地支年
  lunarYear = getTianGan(lunarYear!) + getDiZhi(lunarYear as number);

  // 将计算出来的农历天数转换成汉字
  if (lunarDate < 11) {
    lunarDate = `${lunarDayMap[10]}${lunarDayMap[lunarDate - 1]}`;
  } else if (lunarDate > 10 && lunarDate < 20) {
    lunarDate = `${lunarDayMap[9]}${lunarDayMap[lunarDate - 11]}`;
  } else if (lunarDate === 20) {
    lunarDate = `${lunarDayMap[1]}${lunarDayMap[9]}`;
  } else if (lunarDate > 20 && lunarDate < 30) {
    lunarDate = `${lunarDayMap[11]}${lunarDayMap[lunarDate - 21]}`;
  } else if (lunarDate === 30) {
    lunarDate = `${lunarDayMap[2]}${lunarDayMap[9]}`;
  }

  return {
    lunarYear: lunarYear,
    lunarMonth: lunarMonth,
    lunarDay: lunarDate,
    solarTerm: solarTerm(currentDate),
  };
}

/** 计算农历一年的总天数，参数为存储农历年的16进制；
 * 农历年份信息用16进制存储，其中16进制的第2-4位（0x除外）可以用于表示正常月是大月还是小月。
 * @param ly
 * @returns
 */
function lunarYearDays(ly: number) {
  let totalDays = 0;

  // 获取正常月的天数，并累加
  // 获取16进制的第2-4位，需要用到>>移位运算符
  for (let i = 0x8000; i > 0x8; i >>= 1) {
    let monthDays = ly & i ? 30 : 29;
    totalDays += monthDays;
  }
  // 如果有闰月，需要把闰月的天数加上
  if (hasLeapMonth(ly)) {
    totalDays += leapMonthDays(ly);
  }

  return totalDays;
}

/** 计算农历年是否有闰月，参数为存储农历年的16进制；
 * 农历年份信息用16进制存储，其中16进制的最后1位可以用于判断是否有闰月。
 * @param ly
 * @returns
 */
function hasLeapMonth(ly: number): boolean | number {
  // 获取16进制的最后1位，需要用到&与运算符
  if (ly & 0xf) {
    return ly & 0xf;
  } else {
    return false;
  }
}

function leapMonthDays(ly: number) {
  if (hasLeapMonth(ly)) {
    // 获取16进制的第1位（0x除外）
    return ly & 0xf0000 ? 30 : 29;
  } else {
    return 0;
  }
}

function lunarYearMonths(ly: number) {
  let monthArr: Array<number> = [];

  // 获取正常月的天数，并添加到monthArr数组中
  // 获取16进制的第2-4位，需要用到>>移位运算符
  for (let i = 0x8000; i > 0x8; i >>= 1) {
    monthArr.push(ly & i ? 30 : 29);
  }
  // 如果有闰月，需要把闰月的天数加上
  if (hasLeapMonth(ly)) {
    monthArr.splice(hasLeapMonth(ly) as number, 0, leapMonthDays(ly));
  }

  return monthArr;
}

// 将农历年转换为天干，参数为农历年
function getTianGan(ly: number) {
  let tianGanKey = (ly - 3) % 10;
  if (tianGanKey === 0) tianGanKey = 10;
  return heavenly[tianGanKey - 1];
}

// 将农历年转换为地支，参数为农历年
function getDiZhi(ly: number) {
  let diZhiKey = (ly - 3) % 12;
  if (diZhiKey === 0) diZhiKey = 12;
  return earthly[diZhiKey - 1];
}

/** 二十四节气
 * 八月十五不属于二十四节气
 * @param date 公历日期
 * @returns string 二十四节气
 */
export function solarTerm(date: Date) {
  const SolarTermStr = new Array(
    "小寒",
    "大寒",
    "立春",
    "雨水",
    "惊蛰",
    "春分",
    "清明",
    "谷雨",
    "立夏",
    "小满",
    "芒种",
    "夏至",
    "小暑",
    "大暑",
    "立秋",
    "处暑",
    "白露",
    "秋分",
    "寒露",
    "霜降",
    "立冬",
    "小雪",
    "大雪",
    "冬至"
  );
  let DifferenceInMonth = new Array(
    1272060,
    1275495,
    1281180,
    1289445,
    1299225,
    1310355,
    1321560,
    1333035,
    1342770,
    1350855,
    1356420,
    1359045,
    1358580,
    1355055,
    1348695,
    1340040,
    1329630,
    1318455,
    1306935,
    1297380,
    1286865,
    1277730,
    1274550,
    1271556
  );
  let DifferenceInYear = 31556926;
  let BeginTime = new Date(1901 / 1 / 1);
  BeginTime.setTime(947120460000);
  for (; date.getFullYear() < BeginTime.getFullYear(); ) {
    BeginTime.setTime(BeginTime.getTime() - DifferenceInYear * 1000);
  }
  for (; date.getFullYear() > BeginTime.getFullYear(); ) {
    BeginTime.setTime(BeginTime.getTime() + DifferenceInYear * 1000);
  }
  for (var M = 0; date.getMonth() > BeginTime.getMonth(); M++) {
    BeginTime.setTime(BeginTime.getTime() + DifferenceInMonth[M] * 1000);
  }
  if (date.getDate() > BeginTime.getDate()) {
    BeginTime.setTime(BeginTime.getTime() + DifferenceInMonth[M] * 1000);
    M++;
  }
  if (date.getDate() > BeginTime.getDate()) {
    BeginTime.setTime(BeginTime.getTime() + DifferenceInMonth[M] * 1000);
    M == 23 ? (M = 0) : M++;
  }

  if (date.getDate() == BeginTime.getDate()) {
    return SolarTermStr[M];
  }
  return null;
}

/** 中国传统民间节日
 *
 * @param lunarDate 农历日期
 * @returns
 */
export const traditionalChineseFolkFestivals = (
  lunarDate: string
): string | null => {
  const lunarFestivals = new Map([
    ["正月初一", "春节"],
    ["正月十五", "元宵节"],
    // ["正月初一", "清明节"],
    ["五月初五", "端午节"],
    ["七月七日", "七夕节"],
    ["八月十五", "中秋节"],
    ["九月初九", "重阳节"],
    ["十二月初八", "腊八节"],
  ]);
  if (!lunarFestivals.has(lunarDate)) {
    return null;
  } else {
    return lunarFestivals.get(lunarDate)!;
  }
};
